<?php defined('SYSPATH') or die('No direct script access.');

/**
 * @example 三种情况
 * 节点0是虚拟节点(类似「我的电脑」)，root是指其子集（如C:盘、D:盘）
 */

class Model_Tree extends Model_Database {

	public $tablename = 'catalog';
	public $primary_key = 'cid';
	public $parent_key = 'pid';

	/**
	 * 获取指定ID的Node
	 *
	 * @param  integer $cid 输入ID
	 * @return mixed      输出查询结果
	 */
	public function get_node($cid)
	{
		$result = array();
		$hashkey = $cid;
		
		if (empty($cid)) //查询root属性
		{
			$result = array('path' => '/0/',$this->primary_key => '0',$this->parent_key => NULL,'order' => 0);
		}
		else
		{
			$query = DB::select('*')->from($this->tablename)->where($this->primary_key,'=',$cid);
			$result = $query->execute()->current();
		}
		if (!empty($result))
		{
			//查找子集/新order
			$query = DB::select(array(DB::expr('GROUP_CONCAT(CAST(`'.$this->primary_key.'` AS char(11)) )'),'children'),array(DB::expr('MAX(`order`)'),'new_order'))->from($this->tablename)->where($this->parent_key,'=',$cid)->order_by('order');
			$_result = $query->execute()->current();
			$result['new_order'] = intval($_result['new_order']) + 1;
			$result['children'] = explode(',', $_result['children']);
		}

		return $result;
	}

	/**
	 * 获取所匹配的根节点Node
	 * 
	 * @param  integer $cid 需要查询的Node ID
	 * @return mixed      得到root
	 */
	public function get_root($cid) {
		$node = $this->get_node($cid);
		if (!empty($node)) {
			list(,,$rootid) = explode('/', $node['path']); // [/0/1/2/3/] => 1
			return $this->get_node($rootid);
		}
		return FALSE;
	}

	/**
	 * 获取父级的Node
	 * 
	 * @param  integer $cid 需要查询的Node ID
	 * @return mixed      得到父级元素
	 */
	public function get_parent($cid)
	{
		$node = $this->get_node($cid);
		return empty($node) ? FALSE : $this->get_node($node[$this->parent_key]);
	}

	/**
	 * 获取所匹配的祖先Nodes
	 *
	 * @param  integer $cid 需要查找的Node ID
	 * @return array     返回祖先
	 */
	public function get_parents($cid)
	{
		$result = array();
		$node = $this->get_node($cid);
		while(!empty($node[$this->parent_key]))
		{
			$node = $this->get_node($node[$this->parent_key]);
			$result[] = $node;
		}
		return $result;
	}

	/**
	 * 获得子集，返回一个二维数组
	 *
	 * @param integer $pid 传入父级iD
	 * @param boolean $with_id 返回的数组中是否需要以ID为key
	 * @param boolean $with_children 返回的数组中是否需要以child
	 * @return array 返回数据
	 */
	public function get_children($pid, $with_id = TRUE, $with_children = FALSE) {

		$pid = intval($pid);
		$query1 = DB::select(DB::expr('GROUP_CONCAT(CAST(`b`.`'.$this->primary_key.'` AS char(11)) )'))->from(array($this->tablename,'b'))->where('b.'.$this->parent_key,'=',DB::expr($this->_db->quote_column('a.'.$this->primary_key)))->order_by('b.order');

		$query = DB::select('a.*')->from(array($this->tablename,'a'))->where($this->parent_key,'=',$pid)->order_by('a.order');
		if ($with_children)
			$query->select(array(DB::expr('('.$query1.')'),'children'));

		$result = $query->execute()->as_array($with_id ? $this->primary_key : NULL);
		foreach ($result as $key => $value) {
			if (!empty($value['children']))
				$result[$key]['children'] = explode(',', $value['children']);
		}
		return $result;
	}


	/**
	 * 获得所有子(孙)集，返回一个二维数组
	 *
	 * @param integer $pid 传入父级iD
	 * @param boolean $with_id 返回的数组中是否需要以ID为key
	 * @param boolean $with_children 返回的数组中是否需要以child
	 * @return array 返回数据
	 */
	public function get_class($pid, $with_id = TRUE, $with_children = FALSE) {

		$pid = intval($pid);

		$query1 = DB::select(DB::expr('GROUP_CONCAT(CAST(`b`.`'.$this->primary_key.'` AS char(11)) )'))->from(array($this->tablename,'b'))->where('b.'.$this->parent_key,'=','a.'.$this->primary_key)->order_by('b.order');

		$query = DB::select('a.*')->from(array($this->tablename,'a'))->where('a.path','LIKE','%/'.$pid.'/%')->order_by('a.order');
		if ($with_children)
			$query->select(array(DB::expr('('.$query1.')'),'children'));

		$result = $query->execute()->as_array($with_id ? $this->primary_key : NULL);
		foreach ($result as $key => $value) {
			if (!empty($value['children']))
				$result[$key]['children'] = explode(',', $value['children']);
		}

		return $result;

	}

	/**
	 * 获得所有子(孙)集，返回一个tree数组
	 *
	 * @param integer $pid 传入父级iD
	 * @return array 返回数据
	 */
	public function get_tree($pid, $with_parent = TRUE) {
		$tree = $this->get_class($pid);
		return $this->_class_to_tree($tree,$pid,$with_parent);

	}

	/**
	 * 将上面的二维class数组生成tree，必须是设置with_id的二维数组
	 *
	 * @param mixed $items 通过_get_class获得的二维数组(with_id)
	 * @param integer $topid 提供此二维数组中的顶级节点topid
	 */
	protected function _class_to_tree($items, $topid = 0, $with_parent = TRUE) {
		foreach ($items as $item)
			$items[ ($item[$this->parent_key]) ]['children'][ ($item[$this->primary_key]) ] = &$items[ ($item[$this->primary_key]) ];
		if ($with_parent)
	 		return isset($items[$topid]) ? $items[$topid] : array();
	 	else
	 		return isset($items[$topid]['children']) ? $items[$topid]['children'] : array();
	}

	/**
	 * 添加一个Node,并返回其ID
	 *
	 * @param integet $pid 所属父级id
	 * @param mixed $data 数据
	 * @return integer 返回其insert id
	 */
	public function add_node($pid, $data) {

		$pid = intval($pid);
		$cid = 0;
		$parent = $this->get_node($pid);

		if (!empty($parent))
		{
			$_data = array_merge($data, array(
				$this->parent_key => $pid,
				'order' => $parent['new_order'],
			));
			$query = DB::insert($this->tablename, array_keys($_data))->values(array_values($_data))->execute();
			$cid = array_shift($query);
			DB::update($this->tablename)->set(array(
				'path' => $parent['path'].$cid.'/',
			))->where($this->primary_key,'=',$cid)->execute();
		}

		Model_Log::log(compact('cid'));
		return $cid;
	}
	/**
	 * 删除一个node,会连带删除所有子集,并返回已被删除的ID数组,以便删除其他表的数据
	 *
	 * @param integer $cid 需要删除的Node ID
	 * @return mixed 返回此Node及其子集的id数组
	 */
	public function delete_node($cid) {

		$cid = intval($cid);
		$node = $this->get_node($cid);
		$cls = array_keys($this->get_class($cid));

		DB::delete($this->tablename)->where('path','LIKE','%/'.$cid.'/%')->execute();

		Model_Log::log(compact('cls'));
		return $cls;
	}


	/**
	 * 修改一个node,无法修改pid
	 *
	 * @param integer $cid
	 * @param mixed $data 需要修改的数据
	 * @return boolean 返回操作结果
	 */
	public function update_node($cid, $data) {
		$cid = intval($cid);
		if(empty($cid)) return FALSE; //root则报错

		unset($data[$this->parent_key],$data[$this->primary_key]);
		if (empty($data))
			return FALSE;
		$query = DB::update($this->tablename)->set($data)->where($this->primary_key,'=',$cid)->execute();
		Model_Log::log();
		return TRUE;
	}

	public function change_parent($cid, $new_pid) {

		$cid = intval($cid);
		if(empty($cid)) return FALSE; //报错

		$node = $this->get_node($cid);

		$new_pid = intval($new_pid);
		$new_parent = $this->get_node($new_pid);

		if (empty($new_parent) || empty($node))
			return FALSE;

		$src_path = $node['path'];
		$new_path = $new_parent['path'] . $cid. '/';

		//将所有子集的path修改为新的
		DB::update($this->tablename)->set(array(
			'path' => DB::expr('REPLACE(`path`,\''. $src_path .'\',\'' .$new_path. '\')'),
		))->where('path','LIKE','%/'.$cid.'/%')->execute();

		//排列在新父级的最后
		DB::update($this->tablename)->set(array(
			'order' => $new_parent['new_order'],
			$this->parent_key => $new_pid,
		))->where($this->primary_key,'=',$cid)->execute();

		Model_Log::log();
		return TRUE;
	}

	public function move_node($src_cid, $target_cid, $move_type = 'next') {

		$src_cid = intval($src_cid);
		$target_cid = intval($target_cid);

		if(empty($src_cid)) return FALSE; //报错

		$src_node = $this->get_node($src_cid);
		$target_node = $this->get_node($target_cid);

		if (empty($src_node) || empty($target_node))
			return FALSE;

		if ($move_type == 'inner') //成为别人子集，则直接调用,放入子集节点尾巴
			return $this->change_parent($src_cid, $target_cid);

		//配置order
		$order = $target_node['order'] + ($move_type == 'prev' ? 0 : 1);
		//不在同一父级,则先调到同一级,放置于节点尾巴
		if ($src_node[$this->parent_key] != $target_node[$this->parent_key])
			$this->change_parent($src_cid, $target_node[$this->parent_key]);

		//将此Order后的子集全部加1
		DB::update($this->tablename)->set(array('order' => DB::expr('`order` + 1')))->where($this->parent_key,'=',$target_node[$this->parent_key])->and_where('order','>=',$order)->execute();

		//修改自身的Order
		$this->update_node($src_cid,array('order' => $order));

		Model_Log::log();
		return TRUE;
	}
}
